﻿using System;
using System.Linq;
using System.IO;

using RexToy;
using RexToy.Configuration;
using RexToy.ORM;
using RexToy.ORM.Configuration;
using RexToy.Template;

using Studio.Common;
using Studio.Model;

namespace Studio.CodeGen
{
    class Program
    {
        private static string _configFile = string.Empty;
        private static string _codeTemplateFile = string.Empty;
        private static string _mapTemplateFile = string.Empty;
        private static string _schemaTemplateFile = string.Empty;
        private static string _outputDir = string.Empty;
        private static string _codeNamspace = string.Empty;
        private static string _entityAssembly = string.Empty;
        private static string _dbXmlns = "d";
        private static string _entityXmlns = "e";

        static void Main(string[] args)
        {
            bool b = ExtractParam(args);
            if (!b)
            {
                ShowMessage();
            }
            else
            {
                var v = ValidateParam();
                if (!v)
                {
                    ShowMessage();
                }
                else
                {
                    AppConfig.Load(ConfigFactory.CreateXmlConfig(_configFile));
                    var dbIds = ORMConfig.ORMConfiguration.GetAllDbIds();
                    if (dbIds.Count() != 1)
                    {
                        Console.WriteLine("Can not connect to multiple database servers.");
                        ShowMessage();
                    }
                    else
                    {
                        var dbId = dbIds.First();
                        var info = ORMConfig.ORMConfiguration.GetDatabaseInfo(dbId);

                        var db = Database.SQLServer;
                        switch (info.DialectId)
                        {
                            case RexToy.ORM.Session.Dialect.MSSQL:
                                db = Database.SQLServer;
                                break;

                            case RexToy.ORM.Session.Dialect.OLEDB:
                                db = Database.Access;
                                break;

                            case RexToy.ORM.Oracle.OracleDialect.ORACLE:
                                db = Database.Oracle;
                                break;
                        }
                        var model = DatabaseModel.CreateModel(db);
                        foreach (var s in info.ConnectString.Split(';'))
                        {
                            var kv = s.Split('=');
                            if (kv[0] == "User ID")
                                model.UserName = kv[1];
                        }
                        
                        model.ReadModel();

                        GenerateAllFiles(dbId, model);
                        Console.WriteLine("Generate success.");
                    }
                }
            }
            Console.ReadLine();
        }

        private static void GenerateAllFiles(string dbId, IDatabaseModel model)
        {
            GenerateMappingFile(dbId, model);
            GenerateCodeFile(model);
            GenerateQueryModelFile(model);
        }

        private static void GenerateQueryModelFile(IDatabaseModel model)
        {
            var tm = TemplateEngine.CreateInstance();
            tm.Context.Assign("namespace", _codeNamspace);
            tm.Context.Assign("tables", model.Tables);
            using (TextWriter w = new StreamWriter(Path.Combine(_outputDir, "Schema.cs")))
            {
                w.Write(tm.Render(_schemaTemplateFile));
            }
        }

        private static void GenerateCodeFile(IDatabaseModel model)
        {
            foreach (var t in model.Tables)
            {
                var tm = TemplateEngine.CreateInstance();
                tm.Context.Assign("namespace", _codeNamspace);
                tm.Context.Assign("table", t);
                using (TextWriter w = new StreamWriter(Path.Combine(_outputDir, t.CLRName + ".cs")))
                {
                    w.Write(tm.Render(_codeTemplateFile));
                }
            }
        }

        private static void GenerateMappingFile(string dbId, IDatabaseModel model)
        {
            var tm = TemplateEngine.CreateInstance();
            tm.Context.Assign("assembly", _entityAssembly);
            tm.Context.Assign("namespace", _codeNamspace);
            tm.Context.Assign("db", _dbXmlns);
            tm.Context.Assign("entity", _entityXmlns);
            tm.Context.Assign("tables", model.Tables);
            tm.Context.Assign("dbId", dbId);
            using (TextWriter w = new StreamWriter(Path.Combine(_outputDir, "orm.xml")))
            {
                w.Write(tm.Render(_mapTemplateFile));
            }
        }

        private static bool ValidateParam()
        {
            if (!File.Exists(_configFile))
            {
                Console.WriteLine("Config file not exist: " + _configFile);
                return false;
            }
            if (!File.Exists(_codeTemplateFile))
            {
                Console.WriteLine("Code template file not exist: " + _codeTemplateFile);
                return false;
            }
            if (!File.Exists(_mapTemplateFile))
            {
                Console.WriteLine("Mapping template file not exist: " + _mapTemplateFile);
                return false;
            }
            if (!File.Exists(_schemaTemplateFile))
            {
                Console.WriteLine("Schema template file not exist: " + _schemaTemplateFile);
                return false;
            }
            if (!Directory.Exists(_outputDir))
            {
                Console.WriteLine("Output directory not exist: " + _outputDir);
                return false;
            }
            if (string.IsNullOrEmpty(_codeNamspace))
            {
                Console.WriteLine("Code namespace is invalid.");
                return false;
            }
            if (String.IsNullOrEmpty(_entityAssembly))
            {
                Console.WriteLine("Entity assembly name is missing.");
            }

            return true;
        }

        private static void ShowMessage()
        {
            Console.WriteLine("Code generator, version [{0}]", GetVersion());
            Console.WriteLine("Usage: Studio.CodeGen -o:<output dir> -t:<code template file> -s:<schema template file> -m:<mapping template file> -c:<config file> -n:<code namespace> -a:<entity assembly> [-d:<db xmlns>] [-e:<entity xmlns>]");
            Console.WriteLine("     -n: namespace in which the entity class should stay.");
            Console.WriteLine("     -a: assembly of compiled entity. This is used for ORMapping framework to find the class.");
            Console.WriteLine("     -d: database schema name in mapping xml, default will be [d].");
            Console.WriteLine("     -e: entity schema name in mapping xml, default will be [e]");
        }

        private static string GetVersion()
        {
            var assembly = System.Reflection.Assembly.GetExecutingAssembly();
            string[] strArr = assembly.FullName.Split(',', StringSplitOptions.RemoveEmptyEntries);
            foreach (var str in strArr)
            {
                var pair = str.Split('=');
                if (pair.Length == 2 && pair[0].Trim() == "Version")
                    return pair[1].Trim();
            }
            return string.Empty;
        }

        private static bool ExtractParam(string[] args)
        {
            foreach (var arg in args)
            {
                int idx = arg.IndexOf(':');
                if (idx < 0)
                    return false;

                var p = arg.Substring(0, idx);
                var v = arg.Substring(idx + 1);
                switch (p)
                {
                    case "o":
                    case "-o":
                        _outputDir = v;
                        break;

                    case "t":
                    case "-t":
                        _codeTemplateFile = v;
                        break;

                    case "s":
                    case "-s":
                        _schemaTemplateFile = v;
                        break;

                    case "c":
                    case "-c":
                        _configFile = v;
                        break;

                    case "m":
                    case "-m":
                        _mapTemplateFile = v;
                        break;

                    case "n":
                    case "-n":
                        _codeNamspace = v;
                        break;

                    case "a":
                    case "-a":
                        _entityAssembly = v;
                        break;

                    case "d":
                    case "-d":
                        _dbXmlns = v;
                        break;

                    case "e":
                    case "-e":
                        _entityXmlns = v;
                        break;

                    default:
                        return false;
                }
            }
            return true;
        }
    }
}
